#include "AspectEngine.h"

namespace agtk {

Engine *g_aspectEngine = NULL;

Engine::Engine(EngineSetup *setup)
{
	m_bIsLoaded=false; // The engine is not yet loaded.
	m_setup = new EngineSetup; // m_setup is set to a blank EngineSetup object.
	
	// If setup exists, copy it into m_setup. Else, use default values.
	if( setup != NULL )
		memcpy( m_setup, setup, sizeof( EngineSetup ) );

	g_aspectEngine = this; // Get global pointer to point to this object.
	
	m_states = new LinkedList< State >; // Create the linked list of states.
	m_currentState = NULL; // The current state is null as none are loaded yet.
	
	// Create resource managers.
	m_modelManager = new ResourceManager<Model>;
	m_materialManager = new ResourceManager<Material>;

	m_logger = new Logger("engine.log");
	m_logger->Log(LOGGER_INFO, "Begin Aspect Engine log file.");

	// Set m_mainShader and m_shaderPredefines to NULL as we cannot create them until we have an OpenGL context.
	m_mainShader = NULL;
	m_shaderPredefines = NULL;
}

Engine::~Engine(void)
{
}

bool Engine::Initialize(void)
{
	m_logger->Log(LOGGER_INFO, "Begin engine initialization.");

	// Initialize SDL video.
	if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
	{
		m_logger->Log(LOGGER_ERROR, "Failed to initialize SDL Video (SDL).", SDL_GetError());
		return false;
	}

	// Set standard video flags.
	VideoFlags    = SDL_OPENGL;
	VideoFlags   |= SDL_HWPALETTE;
	VideoFlags   |= SDL_RESIZABLE;

	// Get video info.
	const SDL_VideoInfo * VideoInfo = SDL_GetVideoInfo();
	if(VideoInfo == NULL)
	{
		m_logger->Log(LOGGER_ERROR, "Failed to obtain video information (SDL).", SDL_GetError());
		return false;
	}

	// Do we have hardware acceleration?
	if(VideoInfo -> hw_available)
		VideoFlags |= SDL_HWSURFACE;
	else
		VideoFlags |= SDL_SWSURFACE;

	if(VideoInfo -> blit_hw)
		VideoFlags |= SDL_HWACCEL;

	// Set OpenGL attributes
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); // Use double buffer.
	SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE,   24); // Set depth size.
	SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 8); // Set stencil size.
	SDL_GL_SetAttribute( SDL_GL_ACCUM_RED_SIZE, 8); // Set Red Accumulator size.
	SDL_GL_SetAttribute( SDL_GL_ACCUM_GREEN_SIZE, 8); // Set Green Accumulator size.
	SDL_GL_SetAttribute( SDL_GL_ACCUM_BLUE_SIZE, 8); // Set Blue Accumulator size.
	SDL_GL_SetAttribute( SDL_GL_ACCUM_ALPHA_SIZE, 8); // Set Alpha Accumulator size.
    
	// Do we want full-screen?
	if(m_setup->m_bUseFullscreen)
		VideoFlags |= SDL_FULLSCREEN;

	// Set MainWindow.
	MainWindow = SDL_SetVideoMode(m_setup->m_iWidth, m_setup->m_iHeight, m_setup->m_iBpp, VideoFlags);

	// If MainWindow = NULL, we have a problem.
	if( MainWindow == NULL )
	{
		m_logger->Log(LOGGER_ERROR, "Failed to create window (SDL).", SDL_GetError());
		return false;
	}

	// Set title of window frame.
	SDL_WM_SetCaption(m_setup->m_czTitle, NULL);

	// Perform OpenGL specific initialization.
	glEnable(GL_DEPTH_TEST); // Turn on depth testing.
	glEnable(GL_LIGHTING); // Turn on lighting.
	//glEnable(GL_NORMALIZE); // Automatically normalize normals.
	glEnable(GL_CULL_FACE); // Turn on back face culling.
	glEnable(GL_COLOR_MATERIAL); // Turn on color materials.
	glShadeModel(GL_SMOOTH); // Enable smooth shading.
	glClearColor(0.0, 0.0, 0.0, 0.0); // Set clear color to black.
	glPolygonMode(GL_FRONT, GL_FILL); // Switch to polyson fill mode.

	// For perspective calculation, we divide by the window height. Ergo, the height must not be 0.
	if (m_setup->m_iHeight==0)
	{
		m_setup->m_iHeight=1;
	}

	//  Create OpenGL viewport.
	glViewport(0,0,m_setup->m_iWidth,m_setup->m_iHeight);

	// Switch to projection matrix mode for perspective setting.
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Create OpenGL perspective.
              // FOV        // Ratio                //  The farthest distance before it stops drawing
	gluPerspective(45.0f,(GLfloat)m_setup->m_iWidth/(GLfloat)m_setup->m_iHeight, .5f ,150.0f);

	// Switch to model view matrix mode for drawing.
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		// Problem: glewInit failed, something is seriously wrong.
		m_logger->Log(LOGGER_ERROR, "Cannot not start GLEW.");
	}

	// Set up the main shader.
	m_mainShader = new Shader("./shader.vert", "./shader.frag");
	// Create all the predefined GLSL values.
	m_shaderPredefines = new ShaderPredefines();
	// Use the main shader.
	m_mainShader->UseShader(true);
	// End of OpenGL specific initialization.

	// Set key repeat. Probably need to switch this off at some point.
	if ( SDL_EnableKeyRepeat(1, 1) )
	{
		m_logger->Log(LOGGER_ERROR, "Failed to enable key repeat (SDL).", SDL_GetError());
		return false;
	}

	// If we have gotten this far, the engine must be initialized and ready to go, so set loaded flag to true.
	m_bIsLoaded=true;
	m_logger->Log(LOGGER_INFO, "Aspect Engine initialized successfully.");

	return true;
}

Logger* Engine::GetLogger(void)
{
	return m_logger;
}

void Engine::Run(void)
{
	// Make sure engine has been loaded and initialized without issue before endering loop.
	if (m_bIsLoaded)
		MainLoop();
}

void Engine::Quit(int ret_val)
{
	m_logger->Log(LOGGER_INFO, "Cleaning up...");
	// The engine is no longer loaded.
	m_bIsLoaded = false;

	// Delete the setup object.
	SAFE_DELETE(m_setup);

	// Unload and delete current state.
	m_currentState->Close();
	SAFE_DELETE(m_currentState);

	// Empty and delete state list.
	m_states->Empty();
	SAFE_DELETE(m_states);
	
	// Empty and delete the model manager.
	m_modelManager->EmptyList();
	SAFE_DELETE(m_modelManager);
	
	// Empty and delete the material manager.
	m_materialManager->EmptyList();
	SAFE_DELETE(m_materialManager);

	m_logger->Log(LOGGER_INFO, "Exiting.");
	m_logger->Log(LOGGER_INFO, "End Aspect Engine log file.");

	// Delete logger.
	SAFE_DELETE(m_logger);

	// Quit SDL.
	SDL_Quit();

	// Delete the engine.
	SAFE_DELETE(g_aspectEngine);

	// Exit application.
	exit(ret_val);
}

void Engine::Stop(void)
{
	// Set m_bRun = false so main loop won't continue to run.
	m_bRun=false;
	m_logger->Log(LOGGER_INFO, "Stopped main loop.");
}

long Engine::GetElapsedTicks() {
	return m_lElapsedTicks;
}

int Engine::GetFPS(void)
{
	return m_iFPSTickCounter;
}

int Engine::GetWidth() {
	return m_iWidth;
}

int Engine::GetHeight() {
	return m_iHeight;
}

void Engine::AddState(State *state, bool change)
{
	// Add the new state to the linked list.
	m_states->Add( state );

	// If we DON'T want to change states, return here.
	if( change == false )
		return;

	// Make sure a state is already active before closing.
	if( m_currentState != NULL )
		m_currentState->Close();

	// Get the last added state (the one we just created) and load it.
	m_currentState = m_states->GetLast();
	m_currentState->Load();
}

void Engine::RemoveState(State *state)
{
	// Remove the given state.
	m_states->Remove( &state );
}

void Engine::ChangeState(unsigned long id)
{
	// Iterate through the list of states and find the new state to change to.
	m_states->Iterate( true );
	while( m_states->Iterate() != NULL )
	{
		if( m_states->GetCurrent()->GetID() == id )
		{
			// Close the old state.
			if( m_currentState != NULL )
				m_currentState->Close();

			// Set the new current state and load it.
			m_currentState = m_states->GetCurrent();
			m_currentState->Load();

			// Indicate that the state has changed.
			m_bStateChanged = true;

			break;
		}
	}
}

State *Engine::GetCurrentState(void)
{
	return m_currentState;
}

ResourceManager<Model>* Engine::GetModelManager(void)
{
	return m_modelManager;
}

ResourceManager<Material>* Engine::GetMaterialManager(void)
{
	return m_materialManager;
}

Shader* Engine::GetMainShader(void)
{
	return m_mainShader;
}

ShaderPredefines* Engine::GetShaderPredefines(void)
{
	return m_shaderPredefines;
}

void Engine::MainLoop(void)
{
	// Set first instance of m_lLastTick.
	m_lLastTick = SDL_GetTicks();

	// Set run flag to true.
	m_bRun = true;

	// Loop will run until m_bRun = !true.
	m_logger->Log(LOGGER_INFO, "Entering main loop.");
	while (m_bRun)
	{
		HandleInput();
		Update();
		Render();
	}
	m_logger->Log(LOGGER_INFO, "Broke out of main loop. Quitting...");

	// Cleanup and exit.
	Quit(0);
}

void Engine::HandleInput(void)
{
	// Create SDL event object.
	SDL_Event event;
	
	// Event loop. Polling event for events.
	while ( SDL_PollEvent( &event ) ) 
    {
		// Do state-specific input handling.
		m_currentState->HandleInput(event);

		// Do application-wide input handling (SDL_QUIT, etc.)
		switch ( event.type ) 
		{
			case SDL_QUIT:
				// If SDL_QUIT is sent, stop the loop from running.
				m_bRun = false;
				break;
		}
	}
}

void Engine::Update(void)
{
	// Set m_lElapsedTicks.
	m_lElapsedTicks = SDL_GetTicks() - m_lLastTick;

	// Reset m_lLastTick for the next frame.
	m_lLastTick = SDL_GetTicks();

	// Update the currently active state.
	m_currentState->Update(m_lElapsedTicks);

	// Update m_iFPSTickCounter to keep it current.
	m_iFPSTickCounter += m_lElapsedTicks;
}

void Engine::Render(void)
{
	// Clear buffers.
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Ensure matrix mode is set to model view for rendering.
    glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Render currently active state.
	m_currentState->Render();

	// Swap buffers.
	SDL_GL_SwapBuffers();
}

}